package com.example.makerspace.manager;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.makerspace.dao.BaseDAO;
import com.example.makerspace.dao.DeleteDemandDAO;
import com.example.makerspace.dao.PersonalRecommendationsDAO;
import com.example.makerspace.domain.BaseDO;
import com.example.makerspace.domain.DeleteDemandDO;
import com.example.makerspace.dto.*;
import com.example.makerspace.enums.ResultCodeEnum;
import com.example.makerspace.utils.DateUtils;
import com.google.common.collect.Sets;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

@Component
public abstract class BaseManager<T extends BaseDO, R extends BaseDTO> {

    public final static String QUERY_FIELD = "query_field";
    public final static String QUERY_TYPE = "query_type";
    public final static String QUERY_TYPE_MATCH = "match";
    public final static String QUERY_TYPE_MATCH_PHRASE = "match_phrase";
    public final static String QUERY_TYPE_TERM = "term";
    public final static Integer MATCH_PHRASE_SLOP = 1000;

    protected BaseDAO<T> baseDAO;

    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;


    @Resource
    private DeleteDemandDAO deleteDemandDAO;

    @Resource
    private PersonalRecommendationsDAO personalRecommendationsDAO;


    public T selectById(Integer id) {
        return baseDAO.selectById(id);
    }

    public Result add(Map<String, Object> params, boolean mark) {
        return Result.success();
    }

    public Result update(Map<String, Object> params) {
        return Result.success();
    }

    public Result delete(Map<String, Object> params) {
        return Result.success();
    }

    public Result addDO(T t) {
        baseDAO.insert(t);
        return Result.success();
    }

    public Result addDO(R r) {
        T t = getInstanceT();
        BeanUtils.copyProperties(r, t);
        baseDAO.insert(t);
        return Result.success();
    }

    public Result addDO(T t, ResultCodeEnum resultCodeEnum) {
        baseDAO.insert(t);
        if (resultCodeEnum != null) {
            return Result.success(resultCodeEnum);
        }
        return Result.success();
    }

    public Result updateDO(T t) {
        t.setModifyTime(DateUtils.getDetailDate());
        baseDAO.updateById(t);
        return Result.success();
    }

    public Result deleteById(int id) {
        T t = baseDAO.selectById(id);
        DeleteDemandDO deleteDemandDO = new DeleteDemandDO();
        BeanUtils.copyProperties(t,deleteDemandDO);
        if (t == null) {
            return Result.error(ResultCodeEnum.ID_NOT_EXIST);
        }
        baseDAO.deleteById(id);
        personalRecommendationsDAO.deleteByProductId(id);
        deleteDemandDAO.insert(deleteDemandDO);
        return Result.success();
    }

    public Result deleteById(Map<String, Object> params) {
        Integer id = MapUtils.getInteger(params, "id");
        T t = baseDAO.selectById(id);
        if (t == null) {
            return Result.error(ResultCodeEnum.ID_NOT_EXIST);
        }
        baseDAO.deleteById(id);
        return Result.success();
    }


    public PageResult list(PageRequest pageRequest) {
        Map<String, Object> params = pageRequest.getParams();
        Map<?, ?> selectParam = MapUtils.getMap(params, "select");
        Pagination pagination = pageRequest.getPagination();
        IPage<T> page = baseDAO.page(new Page<>(pagination.getPageNum(), pagination.getSize()), selectParam);
        List<T> records = page.getRecords();
//        List<R> dtoList = Optional.ofNullable(records).orElse(Collections.emptyList())
//                .stream().sorted((o1, o2) -> o2.getCreateTime().compareTo(o1.getCreateTime()))
//                .map(this::convert2DTO)
//                .collect(Collectors.toList());
        PageResult pageResult = PageResult.success();
        pageResult.setObj(records);
        pageResult.setPagination(new Pagination(records.size(), page.getCurrent(), page.getTotal(), page.getPages()));
        return pageResult;
    }

    private T getInstanceT() {
        Class<T> clazz = (Class<T>) getClass(0);
        T t = null;
        try {
            if (clazz != null) {
                t = clazz.newInstance();
            }
        } catch (InstantiationException instantiationException) {
            instantiationException.printStackTrace();
        } catch (IllegalAccessException illegalAccessException) {
            illegalAccessException.printStackTrace();
        }
        return t;
    }

    private R getInstanceR() {
        Class<R> clazz = (Class<R>) getClass(1);
        R r = null;
        try {
            if (clazz != null) {
                r = clazz.newInstance();
            }
        } catch (InstantiationException instantiationException) {
            instantiationException.printStackTrace();
        } catch (IllegalAccessException illegalAccessException) {
            illegalAccessException.printStackTrace();
        }
        return r;
    }

    private Class<?> getClass(int num) {
        Type genericSuperclass = getClass().getGenericSuperclass();
        // 强转成 参数化类型 实体.
        ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
        // 获取超类的泛型类型数组. 即SuperClass<User>的<>中的内容, 因为泛型可以有多个, 所以用数组表示
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        Type genericType = actualTypeArguments[num];
        Class<?> clazz = (Class<R>) genericType;
        return clazz;
    }

    protected R convert2DTO(T t) {
        R r = getInstanceR();
        BeanUtils.copyProperties(t, r);
        convertTime(t, r);
        return r;
    }

    protected void convertTime(T t, R r) {
        r.setCreateTime(DateUtils.formatDetail(t.getCreateTime()));
        r.setModifyTime(DateUtils.formatDetail(t.getModifyTime()));
    }


    protected PageResult search(List<Map<String, String>> filterParams, String filter,
                                List<Map<String, String>> queryParams, String fuzzy,
                                Pagination pagination, Class<T> clazz) {
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        if (StringUtils.isNotBlank(filter)) {
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            for (Map<String, String> filterParam : filterParams) {
                String filed = MapUtils.getString(filterParam, QUERY_FIELD);
                String type = MapUtils.getString(filterParam, QUERY_TYPE);
                switch (type) {
                    case QUERY_TYPE_MATCH:
                        boolQueryBuilder.must(QueryBuilders.matchQuery(filed, filter));
                        break;
                    case QUERY_TYPE_MATCH_PHRASE:
                        boolQueryBuilder.must(QueryBuilders.matchPhraseQuery(filed, filter));
                        break;
                    case QUERY_TYPE_TERM:
                        boolQueryBuilder.must(QueryBuilders.termQuery(filed, filter));
                        break;
                }
            }
            nativeSearchQueryBuilder.withFilter(boolQueryBuilder);
        }

        if (StringUtils.isNotBlank(fuzzy)) {
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            for (Map<String, String> queryParam : queryParams) {
                String filed = MapUtils.getString(queryParam, QUERY_FIELD);
                String type = MapUtils.getString(queryParam, QUERY_TYPE);
                switch (type) {
                    case QUERY_TYPE_MATCH:
                        boolQueryBuilder.should(QueryBuilders.matchQuery(filed, fuzzy));
                        break;
                    case QUERY_TYPE_MATCH_PHRASE:
                        boolQueryBuilder.should(QueryBuilders.matchPhraseQuery(filed, fuzzy).slop(MATCH_PHRASE_SLOP));
                        break;
                    case QUERY_TYPE_TERM:
                        boolQueryBuilder.should(QueryBuilders.termQuery(filed, fuzzy));
                        break;
                }
            }
            nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
        }
        nativeSearchQueryBuilder.withPageable(
                org.springframework.data.domain.PageRequest.of((int) pagination.getPageNum() - 1,
                        (int) pagination.getSize()));
        nativeSearchQueryBuilder.withSorts(SortBuilders.fieldSort("create_time").order(SortOrder.DESC));
        NativeSearchQuery query = nativeSearchQueryBuilder.build();
        SearchHits<T> search = elasticsearchRestTemplate.search(query, clazz);
        List<Integer> ids = search.stream()
                .map(hit -> Integer.parseInt(Objects.requireNonNull(hit.getId())))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(ids)) {
            return PageResult.success();
        }
        List<T> DOS = baseDAO.selectByIds(ids);
        if (CollectionUtils.isEmpty(DOS)) {
            return PageResult.success();
        }
        List<R> dtoList = DOS
                .stream()
                .sorted((o1, o2) -> o2.getCreateTime().compareTo(o1.getCreateTime()))
                .map(this::convert2DTO).collect(Collectors.toList());
        PageResult pageResult = PageResult.success();
        pageResult.setObj(dtoList);
        pageResult.setPagination(new Pagination(
                dtoList.size(),
                pagination.getPageNum(),
                search.getTotalHits(),
                (long) Math.ceil((double) search.getTotalHits() / (double) pagination.getSize())));
        return pageResult;
    }

    public Set<String> distinctValue(String filedName) {
        List<T> ts = baseDAO.distinctValue(StringUtils.camelToUnderline(filedName));
        Class<T> clazz = (Class<T>) getClass(0);
        Set<String> distinctValues = Sets.newHashSet();
        Field declaredField = null;
        try {
            declaredField = clazz.getDeclaredField(filedName);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
        declaredField.setAccessible(true);
        for (T t : ts) {
            try {
                Object o = declaredField.get(t);
                if (o instanceof Collection) {
                    ((Collection<?>) o).forEach(s -> distinctValues.add(String.valueOf(s)));
                } else {
                    distinctValues.add(String.valueOf(o));
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }

        }
        return distinctValues;
    }

    public PageResult select(PageRequest pageRequest) {
        Class<T> clazz = (Class<T>) getClass(0);
        Map<String, Object> params = pageRequest.getParams();
        Pagination pagination = pageRequest.getPagination();
        Map<?, ?> selectParams = MapUtils.getMap(params, "select");
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        for (Map.Entry<?, ?> entry : selectParams.entrySet()) {
            String filed = (String) entry.getKey();
            String value = (String) entry.getValue();
            boolQueryBuilder.must(QueryBuilders.matchPhraseQuery(StringUtils.camelToUnderline(filed), value).slop(MATCH_PHRASE_SLOP));
        }
        nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
        nativeSearchQueryBuilder.withPageable(
                org.springframework.data.domain.PageRequest.of((int) pagination.getPageNum() - 1,
                        (int) pagination.getSize()));
        nativeSearchQueryBuilder.withSorts(SortBuilders.fieldSort("create_time").order(SortOrder.DESC));
        NativeSearchQuery query = nativeSearchQueryBuilder.build();
        SearchHits<T> search = elasticsearchRestTemplate.search(query, clazz);
        List<Integer> ids = search.stream()
                .map(hit -> Integer.parseInt(Objects.requireNonNull(hit.getId())))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(ids)) {
            return PageResult.success();
        }
        List<T> DOS = baseDAO.selectByIds(ids);
        if (CollectionUtils.isEmpty(DOS)) {
            return PageResult.success();
        }
        List<R> dtoList = DOS
                .stream()
                .sorted((o1, o2) -> o2.getCreateTime().compareTo(o1.getCreateTime()))
                .map(this::convert2DTO).collect(Collectors.toList());
        PageResult pageResult = PageResult.success();
        pageResult.setObj(dtoList);
        pageResult.setPagination(new Pagination(
                dtoList.size(),
                pagination.getPageNum(),
                search.getTotalHits(),
                (long) Math.ceil((double) search.getTotalHits() / (double) pagination.getSize())));
        return pageResult;

    }
}
